#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <semaphore.h>
#include <time.h>
#include <ncurses.h>

#define TRUE 1
#define FALSE 0
#define N_THR 100
#define N 4

#define BRIGHT 1
#define COLOR 34
#define BACKGROUND 40
#define REFRESH_RATE 200000

 sem_t mutex; /* Inicia com 1*/
 sem_t mutex_barreira;
 sem_t hacker_queue; /* Inicia com 0*/
 sem_t serf_queue;
 sem_t turnstile; /* Inicia com 0*/
 sem_t turnstile2; /* Inicia com 1*/
sem_t incrementa  ; /*incrementa = 1*/
sem_t tela;
volatile int hackers, serfs, count_barreira;
int onda = 0, linha = 0, linha2 = 5;

void printRiver() {
	sem_wait(&tela);
	int i;
	
	for (i = 0; i < 24; i++)
		if (onda == 0)
		  mvprintw(i, 31, "|                          |");
		else
		{
			if ((i % onda))
				mvprintw(i, 31, "|                          |");
			else
				mvprintw(i, 31, "|.~.~.~.~.~.~.~.~.~.~.~.~.~|");
		}
		
	onda = (onda+1)%4;
	sem_post(&tela);
}

void *refreshscreen()
{
	while(1)
	{
		usleep(REFRESH_RATE);
		sem_wait(&tela);
		refresh();
		sem_post(&tela);
	}
	return NULL;
}

void printBoat(int i, int j, char *v) {
	int k;
	sem_wait(&tela);
	if (v != NULL)
	{
	  mvprintw(i, j,   "/-\\");
	  for (k = 1; k <= N; k++)
			if (i+k < 24)
	      mvprintw(i+k, j, "|%c|", v[k-1]);
	  if (i+k < 24)
			mvprintw(i+k, j, "\\-/");
	}
	sem_post(&tela);
}

void board(int t, int n, char *v){
	int i, j=33;

	for (i = 1; i < 24; i++)
	{
	  printRiver();
		if (v != NULL)
		{
	    printBoat(i, j, v);
			j++;
		  usleep(REFRESH_RATE);
		}
	}
}

void entra_fila(int quem){
	sem_wait(&tela);
	if (quem == 0)
	  mvprintw(linha2, 0, "Hacker entrou na fila.    ");
	else
		mvprintw(linha2, 0, "Serf entrou na fila.      ");
	linha2 = (linha2 + 1);
	if (linha2 == 9) linha2 = 5;
	sem_post(&tela);
  /*quem = 0 -> hacker, quem = 1 -> serf*/
}

void sai_fila(int quem){
	sem_wait(&tela);
	if (quem == 0)
		mvprintw(linha, 0, "Hacker entrou no barco.   ");
	else
		mvprintw(linha, 0, "Serf entrou no barco.     ");
	linha = (linha + 1)%4;
	sem_post(&tela);
	/*quem = 0 -> hacker, quem = 1 -> serf*/
}


inline void entra_barreira(){
  sem_wait(&incrementa);
  sem_wait(&mutex_barreira);
  count_barreira += 1;

  if (count_barreira == N){
    sem_wait(&turnstile2); // lock the second
    sem_post(&turnstile); // unlock the first
  }
  if(count_barreira != N) sem_post(&incrementa);
  sem_post(&mutex_barreira);

  sem_wait(&turnstile); // first turnstile
  sem_post(&turnstile);
}

inline void libera_barreira(){
  sem_wait(&mutex_barreira);
  count_barreira -= 1;

  if (count_barreira == 0){
    sem_wait(&turnstile); // lock the first
    sem_post(&turnstile2); // unlock the second
    sem_post(&incrementa);
  }
  sem_post(&mutex_barreira);

  sem_wait(&turnstile2); // second turnstile
  sem_post(&turnstile2);
}

void* thr_hacker(void* n){
  int isCaptain = FALSE;
  int n_thr, i;
	int nhackers = -1;
  n_thr = (int) n;

  sem_wait(&mutex);
  hackers++;
  if (hackers == 2 && serfs >= 2){
    for(i=0;i<2;i++) sem_post(&hacker_queue);
    for(i=0;i<2;i++) sem_post(&serf_queue);
    serfs -= 2;
    hackers = 0;
		nhackers = 2;
    isCaptain = TRUE;
  }
  else if (hackers == 4){
    for(i=0;i<4;i++) sem_post(&hacker_queue);
    hackers = 0;
    isCaptain = TRUE;
		nhackers = 4;
  }
  else{
    sem_post(&mutex); /* captain keeps the mutex */
  }
  entra_fila(0);
  sem_wait(&hacker_queue);
  sai_fila(0);

	if (nhackers == 2)
    board(0, n_thr, "SSHH");
	else if(nhackers == 4)
		board(0, n_thr, "HHHH");
	else
		board(0, n_thr, NULL);
  entra_barreira();

  if (isCaptain){
    //rowBoat(0, n_thr);
    sem_post(&mutex); /* captain releases the mutex*/
  } 

  libera_barreira();
}

void* thr_serf(void* n){

  int isCaptain = FALSE;
  int n_thr, i;
	int nhackers = -1;
  n_thr = (int) n;

  sem_wait(&mutex);
  serfs++;
  if (serfs == 2 && hackers >= 2){
    for(i=0;i<2;i++) sem_post(&serf_queue);
    for(i=0;i<2;i++) sem_post(&hacker_queue);
    hackers -= 2;
    serfs = 0;
		nhackers = 2;
    isCaptain = TRUE;
  }
  else if (serfs == 4){
    for(i=0;i<4;i++) sem_post(&serf_queue);
    serfs = 0;
		nhackers = 0;
    isCaptain = TRUE;
  }
  else{
    sem_post(&mutex); /* captain keeps the mutex */
    }

  entra_fila(1);
  sem_wait(&serf_queue);
  sai_fila(1);
	
	if (nhackers == 2)
		board(1, n_thr, "HHSS");
	else if(nhackers == 0)
		board(1, n_thr, "SSSS");
	else
		board(1, n_thr, NULL);
  entra_barreira();

  if (isCaptain){
    //rowBoat(1, n_thr);
    sem_post(&mutex); /* captain releases the mutex*/
  } 

  libera_barreira();
}

int main(){
	pthread_t thrscreen;
  pthread_t thrhacker[N_THR];
  pthread_t thrserf[N_THR];
  sem_init(&mutex, 0, 1);
  sem_init(&mutex_barreira, 0, 1);
  sem_init(&hacker_queue, 0, 0);
  sem_init(&serf_queue, 0, 0);
  sem_init(&turnstile, 0, 0);
  sem_init(&turnstile2, 0, 1);
  sem_init(&incrementa, 0, 1);
  sem_init(&tela, 0, 1);
	
  int i;
	
	/* Inicia parte grafica */
	initscr();
	start_color();
	init_pair(1, COLOR_BLACK, COLOR_WHITE);
	attron(COLOR_PAIR(1));
	cbreak();
	noecho();
	keypad(stdscr, TRUE);
	curs_set(0);
  printRiver();
	
	pthread_create(&thrscreen, NULL, refreshscreen, (void*) NULL);
	
  for (i = 0; i < N_THR; i++) {
    pthread_create(&thrhacker[i], NULL, thr_hacker, (void*) i);
		pthread_create(&thrserf[i], NULL, thr_serf, (void*) i);
	}

  for (i = 0; i < N_THR; i++) 
    pthread_join(thrhacker[i], NULL); 
  for (i = 0; i < N_THR; i++) 
    pthread_join(thrserf[i], NULL);
	
	
	attroff(COLOR_PAIR(1));
	endwin();
}
